﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MimiDouban.Abstract;
using System.Net;
using NAudio.Wave;
using System.Threading;
using System.Diagnostics;
using System.IO;
using System.Windows.Forms;
using System.Windows.Threading;
using MimiDouban.Extention;

namespace MimiDouban.Player
{
    class NAudioPlayer : IPlayer
    {
        /// <summary>
        /// 定时器
        /// </summary>
        private System.Threading.Timer timer1, bufferTimer;
        /// <summary>
        /// 定时器回调处理器
        /// </summary>
        private TimerCallback timerHandler;
        /// <summary>
        /// 派发器
        /// </summary>
        private Dispatcher dispatcher;
        /// <summary>
        /// 当前是否暂停
        /// </summary>
        private bool boolIsPaused;

        static AutoResetEvent wait = new AutoResetEvent(false);

        enum StreamingPlaybackState
        {
            Stopped,
            Playing,
            Buffering,
            Paused
        }
        private string currentUrl;
        private float currentVol = 0.8f;
        private volatile bool fullyDownloaded;
        private HttpWebRequest webRequest;
        private BufferedWaveProvider bufferedWaveProvider;
        private volatile StreamingPlaybackState playbackState;
        private IWavePlayer waveOut;
        private VolumeWaveProvider16 volumeProvider;
        delegate void ShowErrorDelegate(string message);
        public delegate void TickDelegate();

        public NAudioPlayer()
        {
            dispatcher = Dispatcher.CurrentDispatcher; 
            object state = new object();
            ThreadPool.RegisterWaitForSingleObject(wait, new WaitOrTimerCallback(triggerTickMethod), state, 1000, false);

        }
        private void StreamMP3(object state)
        {
            //Thread.CurrentThread.Name = "StreamMP3";
            Debug.WriteLine("StreamMP3");
            this.fullyDownloaded = false;
            string url = (string)state;
            webRequest = (HttpWebRequest)WebRequest.Create(url);
            HttpWebResponse resp = null;
            try
            {
                resp = (HttpWebResponse)webRequest.GetResponse();
            }
            catch (WebException e)
            {
                if (e.Status != WebExceptionStatus.RequestCanceled)
                {
                    ShowError(e.Message);
                }
                return;
            }
            byte[] buffer = new byte[16384 * 4]; // needs to be big enough to hold a decompressed frame

            IMp3FrameDecompressor decompressor = null;
            try
            {
                using (var responseStream = resp.GetResponseStream())
                {
                    var readFullyStream = new ReadFullyStream(responseStream);
                    do
                    {
                        if (bufferedWaveProvider != null && bufferedWaveProvider.BufferLength - bufferedWaveProvider.BufferedBytes < bufferedWaveProvider.WaveFormat.AverageBytesPerSecond / 4)
                        {
                            Debug.WriteLine("Buffer getting full, taking a break, " + DateTime.Now);
                            Thread.Sleep(500);
                        }
                        else
                        {
                            Mp3Frame frame = null;
                            try
                            {
                                DateTime time1 = DateTime.Now;
                                frame = Mp3Frame.LoadFromStream(readFullyStream);
                                DateTime time2 = DateTime.Now;
                                int dif = time2.CompareTo(time1);
                                Debug.WriteLine("read frame.FrameLength:" + frame.FrameLength + " ,now:" + DateTime.Now + ",dif:" + dif);
                            }
                            catch (EndOfStreamException)
                            {
                                this.fullyDownloaded = true;
                                // reached the end of the MP3 file / stream
                                Debug.WriteLine("StreamMP3 EndOfStreamException");
                                MimiDouban.Test.TestLog.getInstance().info("StreamMP3 EndOfStreamException," + DateTime.Now);
                                break;
                            }
                            catch (WebException wex)
                            {
                                // probably we have aborted download from the GUI thread
                                Debug.WriteLine("StreamMP3 WebException");
                                MimiDouban.Test.TestLog.getInstance().error("StreamMP3 WebException,", wex);

                                if ( playbackState == StreamingPlaybackState.Playing || playbackState==StreamingPlaybackState.Stopped )
                                {
                                    continue;
                                }

                                if (bufferTimer != null)
                                {
                                    bufferTimer.Dispose();
                                }
                                bufferTimer = new System.Threading.Timer(new TimerCallback(bufferTimerHandler));
                                bufferTimer.Change(20000, Timeout.Infinite);
                                break;
                            }
                            catch (Exception ex)
                            {
                                // probably we have aborted download from the GUI thread
                                Debug.WriteLine("StreamMP3 Exception");
                                MimiDouban.Test.TestLog.getInstance().error("StreamMP3 Exception,", ex);

                                if (playbackState == StreamingPlaybackState.Playing || playbackState == StreamingPlaybackState.Stopped)
                                {
                                    continue;
                                }
                                if (bufferTimer != null)
                                {
                                    bufferTimer.Dispose();
                                }
                                bufferTimer = new System.Threading.Timer(new TimerCallback(bufferTimerHandler));
                                bufferTimer.Change(20000, Timeout.Infinite);
                                break;
                            }
                            if (decompressor == null)
                            {
                                // don't think these details matter too much - just help ACM select the right codec
                                // however, the buffered provider doesn't know what sample rate it is working at
                                // until we have a frame
                                WaveFormat waveFormat = new Mp3WaveFormat(frame.SampleRate, frame.ChannelMode == ChannelMode.Mono ? 1 : 2, frame.FrameLength, frame.BitRate);
                                decompressor = new AcmMp3FrameDecompressor(waveFormat);
                                this.bufferedWaveProvider = new BufferedWaveProvider(decompressor.OutputFormat);
                                this.bufferedWaveProvider.BufferDuration = TimeSpan.FromSeconds(20); // allow us to get well ahead of ourselves
                                //this.bufferedWaveProvider.BufferedDuration = 250;
                            }
                            try
                            {
                                int decompressed = decompressor.DecompressFrame(frame, buffer, 0);
                                //Debug.WriteLine(String.Format("Decompressed a frame {0}", decompressed));
                                bufferedWaveProvider.AddSamples(buffer, 0, decompressed);
                            }
                            catch (System.Exception ex)
                            {
                                MimiDouban.Test.TestLog.getInstance().error("StreamMP3 AddSamples catch", ex);

                            }
                        }

                    } while (playbackState != StreamingPlaybackState.Stopped);
                    Debug.WriteLine("Exiting");
                    // was doing this in a finally block, but for some reason
                    // we are hanging on response stream .Dispose so never get there
                    decompressor.Dispose();
                }
            }
            finally
            {
                if (decompressor != null)
                {
                    decompressor.Dispose();
                }
            }
        }
        private void ShowError(string message)
        {
            //MessageBox.Show(message);
            MimiDouban.Test.TestLog.getInstance().error("show error:" + message);
            //if (this.InvokeRequired)
            //{
            //    this.BeginInvoke(new ShowErrorDelegate(ShowError), message);
            //}
            //else
            //{
            //    MessageBox.Show(message);
            //}
        }
        private void StopPlayback()
        {
            if (playbackState != StreamingPlaybackState.Stopped)
            {
                if (!fullyDownloaded)
                {
                    webRequest.Abort();
                }
                this.playbackState = StreamingPlaybackState.Stopped;
                if (waveOut != null)
                {
                    waveOut.Stop();
                    try
                    {
                    	waveOut.Dispose();
                    }
                    catch (System.Exception ex)
                    {
                        MimiDouban.Test.TestLog.getInstance().error("StopPlayback catch:", ex);
                    }
                    waveOut = null;
                }
                //timer1.Enabled = false;
                // n.b. streaming thread may not yet have exited
                stateChanged();
                Thread.Sleep(500);
                //ShowBufferState(0);
            }
        }
        private IWavePlayer CreateWaveOut()
        {
            return new WaveOut();
            //return new DirectSoundOut();
        }
        private void waveOut_PlaybackStopped(object sender, EventArgs e)
        {
            Debug.WriteLine("Playback Stopped");
        }

        //private void triggerTickMethod(int pos)
        //{
        //    if (PlayerTicked != null )
        //    {
        //        PlayerTicked.Invoke(pos);
        //    }
        //}

        //void timer1_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        //{
        //}
        private void triggerTickMethod(object obj,bool timedOut)
        {
            //Debug.WriteLine("timer1 elapse:" + DateTime.Now + ",state: " + playbackState.ToString());
            
            lock (this)
            {
                try
                {
                    if (playbackState != StreamingPlaybackState.Stopped)
                    {
                        if (this.waveOut == null && this.bufferedWaveProvider != null)
                        {
                            Debug.WriteLine("Creating WaveOut Device");
                            MimiDouban.Test.TestLog.getInstance().info("Creating WaveOut Device," + DateTime.Now);
                            this.waveOut = CreateWaveOut();
                            waveOut.PlaybackStopped += new EventHandler(waveOut_PlaybackStopped);
                            this.volumeProvider = new VolumeWaveProvider16(bufferedWaveProvider);
                            this.volumeProvider.Volume = currentVol;
                            waveOut.Init(volumeProvider);
                            //progressBarBuffer.Maximum = (int)bufferedWaveProvider.BufferDuration.TotalMilliseconds;
                        }
                        else if (bufferedWaveProvider != null)
                        {
                            //try
                            //{
                            //    //Debug.WriteLine("pos: " + ((WaveOut)waveOut).GetPosition().ToString() + " ,sec: " + TimeSpan.FromSeconds((double)((WaveOut)waveOut).GetPosition() / bufferedWaveProvider.WaveFormat.AverageBytesPerSecond));
                            //    this.dispatcher.Invoke(new TickDelegate(triggerTickMethod), Convert.ToInt32((double)((WaveOut)waveOut).GetPosition() / bufferedWaveProvider.WaveFormat.AverageBytesPerSecond));
                            //}
                            //catch (System.Exception ex)
                            //{
                            //    MimiDouban.Test.TestLog.getInstance().error("timer1_Elapsed,bufferedWaveProvider != null, " + DateTime.Now);

                            //}
                            var bufferedSeconds = bufferedWaveProvider.BufferedDuration.TotalSeconds;
                            //ShowBufferState(bufferedSeconds);
                            // make it stutter less if we buffer up a decent amount before playing
                            Debug.WriteLine(String.Format("timer1 elapse: bufferedSeconds={0},time = {1},threadID = {2}", bufferedSeconds, DateTime.Now,Thread.CurrentThread.ManagedThreadId));
                            if (bufferedSeconds < 0.5 && this.playbackState == StreamingPlaybackState.Playing && !this.fullyDownloaded)
                            {
                                this.playbackState = StreamingPlaybackState.Buffering;
                                waveOut.Pause();
                                MimiDouban.Test.TestLog.getInstance().info(String.Format("Paused to buffer, waveOut.PlaybackState={0},bufferedSeconds={1}", waveOut.PlaybackState, bufferedSeconds));
                                Debug.WriteLine(String.Format("Paused to buffer, waveOut.PlaybackState={0},bufferedSeconds={1}", waveOut.PlaybackState, bufferedSeconds));

                                stateChanged();
                            }
                            else if (bufferedSeconds > 4 && this.playbackState == StreamingPlaybackState.Buffering)
                            {
                                waveOut.Play();
                                MimiDouban.Test.TestLog.getInstance().info(String.Format("Started playing, waveOut.PlaybackState={0}", waveOut.PlaybackState));
                                Debug.WriteLine(String.Format("Started playing, waveOut.PlaybackState={0}", waveOut.PlaybackState));
                                this.playbackState = StreamingPlaybackState.Playing;

                                stateChanged();
                            }
                            else if (this.fullyDownloaded && bufferedSeconds == 0)
                            {
                                MimiDouban.Test.TestLog.getInstance().info("Reached end of stream");
                                Debug.WriteLine("Reached end of stream");
                                StopPlayback();
                                if (PlayFinished != null)
                                {
                                    Debug.WriteLine("PlayFinished Invoke");

                                    this.dispatcher.Invoke(new TickDelegate(delegate() { PlayFinished.Invoke(); }), null);
                                }
                            }


                            if (PlayerTicked != null && this.playbackState == StreamingPlaybackState.Playing)
                            {
                                try
                                {
                                    if (waveOut != null && bufferedWaveProvider.WaveFormat != null && bufferedWaveProvider.WaveFormat.AverageBytesPerSecond != 0)
                                    {
                                        int po = Convert.ToInt32(((WaveOut)waveOut).GetPosition());
                                        //Debug.WriteLine("sec: " + po / bufferedWaveProvider.WaveFormat.AverageBytesPerSecond);
                                        //this.dispatcher.BeginInvoke(new TickDelegate(triggerTickMethod), Convert.ToInt32(((WaveOut)waveOut).GetPosition() / bufferedWaveProvider.WaveFormat.AverageBytesPerSecond));
                                        //this.dispatcher.BeginInvoke(new TickDelegate(triggerTickMethod), Convert.ToInt32(po / bufferedWaveProvider.WaveFormat.AverageBytesPerSecond));
                                        //PlayerTicked.Invoke(Convert.ToInt32(((WaveOut)waveOut).GetPosition() / bufferedWaveProvider.WaveFormat.AverageBytesPerSecond));


                                        this.dispatcher.Invoke(new TickDelegate(delegate() { PlayerTicked.Invoke(po / bufferedWaveProvider.WaveFormat.AverageBytesPerSecond); }), null);
                                    }
                                }
                                catch (System.Exception ex)
                                {
                                    MimiDouban.Test.TestLog.getInstance().error("timer1_Elapsed catch");

                                }
                            }
                        }

                    }
                }
                catch (System.Exception ex)
                {
                    Test.TestLog.getInstance().error("triggerTickMethod catch ", ex);

                }

            }
        }

        //private void timer1Handler(object arg)
        //{
        //    ThreadPool.QueueUserWorkItem(new WaitCallback(triggerTickMethod), null);
        //    //this.dispatcher.Invoke(new TickDelegate(delegate() { triggerFirstTickMethod(); }), null);
        //    //Thread.CurrentThread.Name = "TimerEla";
        //}
        private void forceNextMethod()
        {
            Debug.WriteLine("forceNextMethod: " + playbackState.ToString() + DateTime.Now);
            
            this.dispatcher.Invoke(new TickDelegate(delegate() { PlayFailed.Invoke(new Exception("stream exception")); }), null);

        }
        private void bufferTickMethod()
        {
            Debug.WriteLine("bufferTickMethod: " + playbackState.ToString() + DateTime.Now);
            if (playbackState == StreamingPlaybackState.Buffering)
            {
                PlayFinished.Invoke();
                
            }

        }
        private void bufferTimerHandler(object arg)
        {
            Debug.WriteLine("bufferTimerHandler: " + DateTime.Now);
            this.dispatcher.Invoke(new TickDelegate(bufferTickMethod), null);
        }

        private void stateChanged()
        {
            Debug.WriteLine("stateChanged: " + playbackState.ToString() + DateTime.Now);
            try
            {
                if (playbackState == StreamingPlaybackState.Buffering)
                {
                    this.dispatcher.Invoke(new TickDelegate(delegate() { BufferingBegin.Invoke(); }), null);

                    if (bufferTimer != null)
                    {
                        bufferTimer.Dispose();
                    }
                    bufferTimer = new System.Threading.Timer(new TimerCallback(bufferTimerHandler));
                    bufferTimer.Change(15000, Timeout.Infinite);

                }
                else
                {
                    if (bufferTimer != null)
                    {
                        bufferTimer.Dispose();
                    }
                    this.dispatcher.Invoke(new TickDelegate(delegate() { BufferingEnd.Invoke(); }), null);

                }
            }
            catch (System.Exception ex)
            {
                MimiDouban.Test.TestLog.getInstance().error("state changed catch:", ex);

            }
        }

        //private void threadPlay(object obj)
        //{
        //}


        #region IPlayer 成员

        public bool isPaused()
        {
            return boolIsPaused;
        }

        public void play(string url)
        {
            //ThreadPool.QueueUserWorkItem(new WaitCallback(threadPlay), url);

            //playDispatcher = Dispatcher.CurrentDispatcher;
            //Thread.CurrentThread.Name = "play";
            if (url != currentUrl)
            {
                currentUrl = url;
                StopPlayback();
                playbackState = StreamingPlaybackState.Stopped;
                stateChanged();
            }
            if (playbackState == StreamingPlaybackState.Stopped)
            {
                currentUrl = url;
                playbackState = StreamingPlaybackState.Buffering;
                stateChanged();
                this.bufferedWaveProvider = null;
                ThreadPool.QueueUserWorkItem(new WaitCallback(StreamMP3), url);
                // 
                // timer1
                // 
                //if (timer1 != null)
                //{
                //    timer1.Dispose();
                //}
                //timer1 = new System.Timers.Timer();
                //this.timer1.Interval = 250;
                //this.timer1.Elapsed += new System.Timers.ElapsedEventHandler(timer1_Elapsed);
                ////timer1.AutoReset = false;
                //timer1.Enabled = true;
                //timer1.Start();
                //timer1 = new System.Threading.Timer(new TimerCallback(timer1Handler));
                //timer1.Change(10, 100);

                
                wait.Set();
            }
            else if (playbackState == StreamingPlaybackState.Paused)
            {
                playbackState = StreamingPlaybackState.Buffering;
                stateChanged();
            }
            boolIsPaused = false;
        }

        public void pause()
        {
            if (playbackState == StreamingPlaybackState.Playing || playbackState == StreamingPlaybackState.Buffering)
            {
                waveOut.Pause();
                Debug.WriteLine(String.Format("User requested Pause, waveOut.PlaybackState={0}", waveOut.PlaybackState));
                playbackState = StreamingPlaybackState.Paused;
                boolIsPaused = true;
                stateChanged();
                //timer1.Enabled = false;
            }
        }

        public void resume()
        {
            boolIsPaused = false;
            //timer1.Enabled = true;
            this.play(currentUrl);
        }

        public void stop()
        {
            //timer1.Enabled = false;
            StopPlayback();
        }

        public void setVolume(double vol)
        {
            if (this.volumeProvider != null)
            {
                this.volumeProvider.Volume = (float)vol;
            }
            currentVol = (float)vol;
        }

        public double getVolume()
        {
            if (this.volumeProvider != null)
            {
                return this.volumeProvider.Volume;
            }

            return currentVol;
        }

        public void distroy()
        {
            StopPlayback();
        }

        public event PlayerEventHandler BufferingBegin;

        public event PlayerEventHandler BufferingEnd;

        public event PlayerFailedEventHandler PlayFailed;

        public event PlayerEventHandler PlayFinished;

        public event PlayerTickEventHandler PlayerTicked;

        #endregion
    }
}
